Skip to content

improvement(search): redesign Cmd-K palette with curated empty state and drill-down browse#5194

Open
waleedlatif1 wants to merge 8 commits into
stagingfrom
worktree-block-picker-redesign
Open

improvement(search): redesign Cmd-K palette with curated empty state and drill-down browse#5194
waleedlatif1 wants to merge 8 commits into
stagingfrom
worktree-block-picker-redesign

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • The Cmd-K command palette (also the canvas "add block" picker) dumped the entire catalog into the list on open — ~36 blocks, ~221 tools, 11 triggers, 1,000+ tool operations, and ~250 docs — ~1,500 unvirtualized DOM rows rendered before the user typed a character, and re-fuzzy-matched on every keystroke. It was overwhelming to browse and the single most expensive render in the palette.
  • Now the empty state is curated and navigable, and the full catalog surfaces only on search — extending the pattern the integrations group already used.

What changed

  • Catalog gated behind search. Blocks, tools, triggers, tool operations, and docs only render once the user types. Tool operations and docs are now search-only (never browsed by scroll).
  • Recents. New persisted, frecency-ranked store (frequency + recency, 7-day half-life) surfaces the blocks you actually use at the top of the empty state. Directly helps follow-along during recorded/guided builds.
  • Browse → drill-down. A Browse section lists categories (Core Blocks, Triggers, and one per integration type — AI, Communication, Sales, …) derived from category + integrationType. Selecting one scopes the list to just that category. Typing from the root still does a flat global search.
  • Scope pill. The active drill-down shows as a removable pill in the search bar (Linear/Raycast pattern). Backspace on an empty input or Escape pops the scope. Deliberately not an in-list back row — that would steal Enter from cmdk's first-item highlight while searching within a scope.
  • Per-group result caps keep the DOM bounded on broad queries (results are score-sorted, so only the low-relevance tail is trimmed).

Toolbar block picker (separate component) intentionally left as a follow-up to keep this PR focused.

Type of Change

  • Improvement

Testing

  • store.test.ts updated + new coverage for integrationType capture and category building (5/5 passing)
  • Full tsc clean (0 errors), biome clean
  • Manual visual pass pending

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…and drill-down browse

- Stop dumping the full ~1,500-item catalog (blocks, tools, triggers, 1,000+ tool operations, docs) into the palette on open; surface them only once the user types, mirroring the existing integrations group
- Add a curated empty state: frecency-ranked Recents (new persisted store) and a Browse section that drills into block/trigger/integration categories
- Scope drill-down shown as a removable pill in the search bar; Backspace on empty input or Escape pops the scope
- Cap each result group to keep the DOM bounded on broad queries
- Derive browse categories from block category + integrationType
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 24, 2026 4:43pm

Request Review

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large UX change to the primary add-block/search path on the workflow canvas; behavior shifts (empty vs search-only catalog, drill-down, result caps) could surprise users or hide matches on very broad queries, though add-block dispatch and analytics are unchanged.

Overview
Redesigns the workspace Cmd-K search modal so opening it no longer renders the full blocks/tools/triggers/docs catalog (~1,500 rows). On the workflow page, the empty state shows Recent (up to five frecency-ranked picks from a persisted store, recorded when you add blocks/tools/triggers/operations) and Browse categories from the search store, with new MemoizedCategoryItem, RecentsGroup, and BrowseGroup UI.

Browse drill-down adds scope state: picking a category filters to that list (blocks, triggers, or tools by integration type), shows a removable scope pill in the search bar, and uses Escape / Backspace on an empty field to pop scope before closing. Catalog groups (blocks, tools, triggers, tool ops, docs) only appear when the user is typing at root scope; scoped views hide the global catalog. filterAndCap limits each result group to 50 ranked rows so broad searches stay bounded.

Input reset is centralized in clearInput so cmdk’s uncontrolled input stays in sync when opening the modal or changing scope.

Reviewed by Cursor Bugbot for commit 2848a0b. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the Cmd-K palette's "dump everything" empty state with a curated, performance-conscious design: catalog items (blocks, tools, triggers, tool-ops, docs) are now gated behind search input, while the empty state surfaces frecency-ranked recents and a drill-down browse by category. A new useSearchRecentsStore persists selections with a 7-day half-life frecency formula, per-group DOM caps keep broad queries bounded, and a scope pill with Backspace/Escape navigation implements the Linear/Raycast UX pattern.

  • New frecency store (recents.ts) persists and ranks recent selections; pruning now correctly uses frecencyScore and is covered by regression tests.
  • Browse categories are built from integrationType on tool blocks; INTEGRATION_CATEGORY_ICONS is an exhaustive Record<IntegrationType, …> so a missing category is a compile error.
  • docx.cjs — a generated sandbox bundle — was updated alongside the search changes; this appears incidental and unrelated to the stated scope of the PR.

Confidence Score: 5/5

Safe to merge; the search modal is a purely additive UI change with no impact on workflow execution, data persistence, or auth paths.

The redesign is well-scoped: new state (scope, recents) is local to the modal component and a persisted Zustand store, the frecency algorithm and pruning are unit-tested, and the catalog gating/capping is a straightforward conditional. The only out-of-place change is the generated docx.cjs bundle, which has no runtime interaction with the search modal and can be reverted independently.

apps/sim/lib/execution/sandbox/bundles/docx.cjs — unrelated bundle update that should be confirmed intentional or reverted.

Important Files Changed

Filename Overview
apps/sim/stores/modals/search/recents.ts New frecency store with pruning correctly using frecency score (addressing previously flagged bug); clear, bounded, and persisted via Zustand middleware.
apps/sim/stores/modals/search/store.ts Adds integrationType capture on tool blocks and buildCategories helper; categories are well-ordered (AI first, then by count) and self-maintaining.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx Major redesign: catalog gated behind search, scope pill with Backspace/Escape navigation, frecency-sorted recents, and per-group DOM caps — logic is well-structured and ref usage for stale-closure avoidance is correct.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx Adds RecentsGroup and BrowseGroup; INTEGRATION_CATEGORY_ICONS is an exhaustive Record over IntegrationType making missing categories a compile error rather than a silent fallback.
apps/sim/stores/modals/search/recents.test.ts New test file with solid frecency math coverage and pruning regression test; one test name is slightly misleading since the store now prunes by frecency.
apps/sim/lib/execution/sandbox/bundles/docx.cjs Minified sandbox bundle regenerated; unrelated to search modal changes — appears accidental or from a concurrent build step.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Open([Palette opens]) --> Reset[Reset search + scope to null]
    Reset --> EmptyState{Input empty?}

    EmptyState -->|Yes| WorkflowPage{On workflow page?}
    WorkflowPage -->|No| ActionsOnly[Show Actions group only]
    WorkflowPage -->|Yes| ShowEmpty[Show Recents + Browse categories + Actions]

    ShowEmpty --> UserTypesA[User types]
    UserTypesA --> GlobalSearch[Global search across all groups\nBlocks / Tools / Triggers / ToolOps / Docs\ncapped at 50 results each]

    ShowEmpty --> SelectCategory[User selects a Browse category]
    SelectCategory --> ScopeActive[Scope pill shown in input bar\nscope state set]

    ScopeActive --> ScopedBrowse[Show all items in category\nfilterAndSort — no hard cap]
    ScopedBrowse --> UserTypesB[User types]
    UserTypesB --> ScopedSearch[Filter within scope only]

    ScopeActive --> EscapeOrBackspace[Escape / Backspace on empty input]
    EscapeOrBackspace --> Reset

    GlobalSearch --> SelectItem[User selects item]
    ScopedSearch --> SelectItem
    ScopedBrowse --> SelectItem
    ShowEmpty --> SelectRecent[User selects Recent]
    SelectRecent --> RecordFrecency[recordRecent called\nfrecency store updated]
    SelectItem --> RecordFrecency
    RecordFrecency --> Close([Palette closes])
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    Open([Palette opens]) --> Reset[Reset search + scope to null]
    Reset --> EmptyState{Input empty?}

    EmptyState -->|Yes| WorkflowPage{On workflow page?}
    WorkflowPage -->|No| ActionsOnly[Show Actions group only]
    WorkflowPage -->|Yes| ShowEmpty[Show Recents + Browse categories + Actions]

    ShowEmpty --> UserTypesA[User types]
    UserTypesA --> GlobalSearch[Global search across all groups\nBlocks / Tools / Triggers / ToolOps / Docs\ncapped at 50 results each]

    ShowEmpty --> SelectCategory[User selects a Browse category]
    SelectCategory --> ScopeActive[Scope pill shown in input bar\nscope state set]

    ScopeActive --> ScopedBrowse[Show all items in category\nfilterAndSort — no hard cap]
    ScopedBrowse --> UserTypesB[User types]
    UserTypesB --> ScopedSearch[Filter within scope only]

    ScopeActive --> EscapeOrBackspace[Escape / Backspace on empty input]
    EscapeOrBackspace --> Reset

    GlobalSearch --> SelectItem[User selects item]
    ScopedSearch --> SelectItem
    ScopedBrowse --> SelectItem
    ShowEmpty --> SelectRecent[User selects Recent]
    SelectRecent --> RecordFrecency[recordRecent called\nfrecency store updated]
    SelectItem --> RecordFrecency
    RecordFrecency --> Close([Palette closes])
Loading

Reviews (3): Last reviewed commit: "fix(search): type category icon map by t..." | Re-trigger Greptile

Comment thread apps/sim/stores/modals/search/recents.ts Outdated
- Fix stale input text when entering/exiting a browse scope: the cmdk
  Command.Input is uncontrolled, so resetting requires the native value
  setter, not just setSearch(''). Extract the existing open-reset logic into
  a shared clearInput() and reuse it for open, enter-scope, and exit-scope
- Short-circuit the recents memo when there are no recorded entries
- Use a distinct icon for the Productivity category (was reusing Core Blocks')
- Add unit coverage for the recents store and frecency scoring
…etically

- Lead with workflow primitives (Core Blocks, Triggers), then pin AI first
  (Sim is an AI workspace) and rank remaining integration categories by
  breadth (most integrations first) so the richest categories surface first
- Personal frequency is already covered by Recents, so the static order
  optimizes for discovery and self-maintains as integrations are added
- Reuses the canonical integration taxonomy (INTEGRATION_TYPE_LABELS) shared
  with the /integrations page rather than inventing a parallel one
…tive

Review-loop findings:
- Recents were pruned by raw recency but ranked by frecency, so a frequently
  used older block could be evicted while still ranking high. Prune by the same
  frecencyScore used for display; add a regression test
- Make the integration category icon map exhaustive over IntegrationType so a
  newly added category is a compile error instead of a silent generic-icon
  fallback; drive core-block/trigger icons off the category kind
- Factor the shared gate-and-cap rule for the catalog search groups into a
  single cappedCatalog() helper instead of repeating it across five memos
…the palette

Workflows/files/chats/tables/KBs were rendered in full at the empty state, so a
workspace with thousands of them dumped thousands of rows into the DOM. Factor
the catalog cap into a shared filterAndCap() and apply it to all variable-size
groups, bounding palette DOM to MAX_RESULTS_PER_GROUP per group regardless of
workspace size.
…es them

The directory imports resolve to the search-groups and command-items barrels;
BrowseGroup, RecentsGroup, RecentRenderItem, and MemoizedCategoryItem were
added to the source files but not re-exported, breaking the Turbopack build
(tsc resolved them via the source file and missed it).
…cideIcon

The map mixes lucide and emcn icons (Database comes from @/components/emcn/icons,
which is not a LucideIcon). Type it as ComponentType<{ className?: string }> —
the exact type MemoizedCategoryItem's icon prop consumes — which both icon
sources satisfy, keeping the exhaustive IntegrationType key check.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2848a0b. Configure here.

@waleedlatif1 waleedlatif1 deleted the branch staging July 1, 2026 05:43
@waleedlatif1 waleedlatif1 reopened this Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant